#include "ServerGameManager.h"

ServerGameManager::ServerGameManager()
{
    AddPlayer(Player(0, "host"));
}

void ServerGameManager::AddPlayer(const Player& player)
{
    m_game->AddPlayer(player);
}

void ServerGameManager::AddCommand(int id, Command::CommandType command)
{
    QList<Command::CommandType> commands = m_commandQueue[id];
    QMutableListIterator< Command::CommandType > i(commands);
    while(i.hasNext())
    {
        if(i.next() != command)
            i.remove();
    }
    commands.push_back(command);
}

void ServerGameManager::ProcessCommand(TankPtr tank)
{
    int id = tank->Id();
    Command::CommandType command = Command::eUnknown;
    if(m_commandQueue.contains(id))
    {
        QList<Command::CommandType> commands = m_commandQueue[id];
        if(!commands.empty())
        {
            command = commands.takeFirst();
        }
    }

    switch(command)
    {
    case Command::eUnknown:
        tank->SetAction(Tank::eStaying);
        break;
    case Command::eReqMove:
        tank->SetAction(Tank::eMoving);
        break;
    case Command::eReqTurnLeft:
        tank->SetAction(Tank::eTurningLeft);
        break;
    case Command::eReqTurnRight:
        tank->SetAction(Tank::eTurningRight);
        break;
    }
}

template <typename T>
void ServerGameManager::fillDistances(QList<T>& objects, QList< QPair<T, unsigned> >& distances, double elapsed)
{
    T ptr;
    foreach(ptr, objects)
    {
        distances.append(qMakePair(
                                 ptr,
                                 calcObjectPosition(ptr.data(), elapsed)
                                       ));
    }
}

template <typename T>
void ServerGameManager::fillRotatingObjects(QList<T>& objects, QList<T>& output)
{
    T obj;
    foreach(obj, objects)
    {
        if ( (obj->CurrentAction() == Tank::eTurningRight) || (obj->CurrentAction() == Tank::eTurningLeft))
        {
            output.push_back(obj);
        }
    }
}

bool ServerGameManager::movesIntoObstacle(DynamicObject *object)
{
    int beginX, beginY, endX, endY;
    switch(object->Direction())
    {
    case DynamicObject::eNorth:
        beginX = object->CurrentPosition().x;
        beginY = object->CurrentPosition().y - 1;
        if(beginY < 0)
        {
            return true;
        }
        endX = object->CurrentPosition().x + object->width();

        for(int i=beginX; i <= endX; ++i)
        {
            if(!m_game->GameBoard()->GetMap()->ItemAt(i, beginY)->isMovable())
            {
                return true;
            }
        }
        break;

    case DynamicObject::eSouth:
        beginX = object->CurrentPosition().x;
        beginY = object->CurrentPosition().y  + object->height() + 1;
        if(beginY > 128)
        {
            return true;
        }
        endX = object->CurrentPosition().x + object->width();

        for(int i=beginX; i <= endX; ++i)
        {
            if(m_game->GameBoard()->GetMap()->ItemAt(i, beginY)->isMovable())
            {
                return true;
            }
        }
        break;

    case DynamicObject::eWest:
        beginX = object->CurrentPosition().x - 1;
        beginY = object->CurrentPosition().y;

        if(beginX < 0)
        {
            return true;
        }
        endY = object->CurrentPosition().y + object->height();

        for(int i=beginY; i <= endY; ++i)
        {
            if(m_game->GameBoard()->GetMap()->ItemAt(beginX, i)->isMovable())
            {
                return true;
            }
        }
        break;

    case DynamicObject::eEast:
        beginX = object->CurrentPosition().x + object->width() + 1;
        beginY = object->CurrentPosition().y;

        if(beginX > 128)
        {
            return true;
        }
        endY = object->CurrentPosition().y + object->height();

        for(int i=beginY; i <= endY; ++i)
        {
            if(m_game->GameBoard()->GetMap()->ItemAt(beginX, i)->isMovable())
            {
                return true;
            }
        }
        break;
    }
    return false;
}

bool ServerGameManager::objectsCollide(DynamicObject *first, DynamicObject *second)
{
    if(first == second)
    {
        return false;
    }

    QRect firstRect(first->CurrentPosition().x, first->CurrentPosition().y,
                   first->width(), first->height());
    QRect secondRect(second->CurrentPosition().x, second->CurrentPosition().y,
                     second->width(), second->height());

    if(firstRect.intersects(secondRect))
    {
        return true;
    }
    else
    {
        return false;
    }
}

template <class objT>
void removeWithKey(QList< QPair<objT, unsigned> > list, objT item)
{
    QMutableListIterator< QPair<objT, unsigned> > i(list);
    while(i.hasNext())
    {
        if(i.next().first == item)
        {
            i.remove();
            return;
        }
    }
}

void ServerGameManager::GameLoop()
{
    qint64 currentTime = QDateTime::currentMSecsSinceEpoch();
    qint64 timeElapsed = m_lastUpdate - currentTime;
    m_lastUpdate = currentTime;
    double secondsElapsed = timeElapsed / 1000;


    GameBoardPtr boardPtr = m_game->GameBoard();

    // Calculate how many cells each bullet and tank has to pass before actually moving them
    // It is needed to avoid unfair movement based on position in vector
    QList<BulletPtr> bulletList = *boardPtr->Bullets();
    QList< QPair<BulletPtr, unsigned> > bulletDistances;
    fillDistances(bulletList, bulletDistances, secondsElapsed);

    QList<TankPtr> tankList = *boardPtr->Tanks();
    QList< QPair<TankPtr, unsigned> > tankDistances;
    fillDistances(tankList, tankDistances, secondsElapsed);

    QList<TankPtr> rotatingTankList;
    fillRotatingObjects(tankList, rotatingTankList);

    // Move each object by one cell till there are no objects to move
    // Collision check is done each movement cycle
    while( !bulletDistances.empty() || !tankDistances.empty() )
    {
        //Bullets are always moved. They get destroyed if collide with obstacles
        QMutableListIterator< QPair<BulletPtr, unsigned> > i(bulletDistances);
        while(i.hasNext())
        {
            QPair<BulletPtr, unsigned> currentBulletDistance = i.next();
            BulletPtr currentBullet = currentBulletDistance.first;

            currentBullet->move();
            --currentBulletDistance.second;

            currentBullet->UpdateImagePosition();

            if(movesIntoObstacle(currentBullet.data()))
            {
                bulletList.removeOne(currentBullet);
                currentBullet->destroy();
                i.remove();
                continue;
            }

            BulletPtr collided;
            foreach(collided, bulletList)
            {
                if(objectsCollide(currentBullet.data(), collided.data()))
                {
                    bulletList.removeOne(currentBullet);
                    bulletList.removeOne(collided);
                    removeWithKey(bulletDistances, collided);
                    i.remove();
                    collided->destroy();
                    currentBullet->destroy();
                    continue;
                }
            }

            if(currentBulletDistance.second < 1)
            {
                i.remove();
            }
        }

        QMutableListIterator< QPair<TankPtr, unsigned> > j(tankDistances);
        while(j.hasNext())
        {
            QPair<TankPtr, unsigned> currentTankDistance = j.next();
            TankPtr currentTank = currentTankDistance.first;

            if(currentTank->Firing())
            {
                if(m_tankLastFired[currentTank->Id()] > m_game->Settings().Firerate())
                {
                    m_tankLastFired[currentTank->Id()] = currentTime;
                    BulletPtr firedBullet(new Bullet);
                    firedBullet->SetPosition(currentTank->CurrentPosition());
                    firedBullet->SetDirection(currentTank->Direction());
                    firedBullet->move();
                    m_game->GameBoard()->Bullets()->push_back(firedBullet);
                }
            }

            BulletPtr collidedBullet;
            foreach(collidedBullet, bulletList)
            {
                if(objectsCollide(currentTank.data(), collidedBullet.data()))
                {
                    currentTank->hit();
                    bulletList.removeOne(collidedBullet);
                    collidedBullet->destroy();

                    if(currentTank->Health() < 1)
                    {
                        j.remove();
                        tankList.removeOne(currentTank);
                        currentTank->destroy();
                        continue;
                    }
                }
            }

            switch(currentTank->CurrentAction())
            {
            case Tank::eStopping:

                if(currentTankDistance.second > 0)
                {
                    currentTank->move();
                    currentTank->SetProgress(0);
                    currentTankDistance.second = 0;
                    ProcessCommand(currentTank);
                    currentTank->UpdateImagePosition();
                }
                j.remove();
                continue;

                break;
            case Tank::eMoving:

                if(movesIntoObstacle(currentTank.data()))
                {
                    currentTank->SetAction(Tank::eStopping);
                    continue;
                }

                TankPtr collided;
                foreach(collided, tankList)
                {
                    if(objectsCollide(currentTank.data(), collided.data()))
                    {
                        currentTank->SetAction(Tank::eStopping);
                        continue;
                    }
                }

                currentTank->move();
                --currentTankDistance.second;
                currentTank->UpdateImagePosition();

                break;
            }

            if(currentTankDistance.second < 1)
            {
                j.remove();
            }
        }

        QMutableListIterator< TankPtr > k(rotatingTankList);
        while(k.hasNext())
        {
            TankPtr currentTank = k.next();
            bool turningRight = true;
            switch(currentTank->CurrentAction())
            {
            case Tank::eTurningLeft:
                turningRight = false;
            case Tank::eTurningRight:
            {
                unsigned progress = currentTank->Progress() +
                        floor((double) ((currentTank->Speed() / 2) * secondsElapsed) );

                if(progress >= 100)
                {
                    currentTank->SetImageRotation( Tank::AngleFromDirection(currentTank->Direction()) );
                    currentTank->SetProgress(0);
                    ProcessCommand(currentTank);
                    k.remove();
                }
                else
                {
                    qreal rotation;
                    if(turningRight)
                    {
                        rotation = Tank::AngleFromDirection(currentTank->Direction()) - ( (100 - progress) * 90 );
                    }
                    else
                    {
                        rotation = Tank::AngleFromDirection(currentTank->Direction()) + ( (100 - progress) * 90 );
                    }
                    currentTank->SetImageRotation(rotation);
                }
                break;
            }
            }
        }
    }
}


void ServerGameManager::SlotProcessRequest(CommandPtr request)
{
    switch(request->Type())
    {
    case Command::eReqSendNickname:
    {
        PlayerIDBean* bean = static_cast<PlayerIDBean*>(request.data()->Params().data());
        AddPlayer(Player(bean->PlayerId(), ""));
        break;
    }

    case Command::eReqDisconnect:
    {
        PlayerIDBean* bean = static_cast<PlayerIDBean*>(request.data()->Params().data());
        m_game->PlayersTank(bean->PlayerId())->destroy();
        break;
    }

    case Command::eReqMove:
    {
        PlayerIDBean* bean = static_cast<PlayerIDBean*>(request.data()->Params().data());
        m_commandQueue[m_game->PlayersTank(bean->PlayerId())->Id()].clear();
        m_commandQueue[m_game->PlayersTank(bean->PlayerId())->Id()].push_back(Command::eReqMove);
        break;
    }

    case Command::eReqStopMove:
    {
        PlayerIDBean* bean = static_cast<PlayerIDBean*>(request.data()->Params().data());
        m_commandQueue[m_game->PlayersTank(bean->PlayerId())->Id()].removeAll(Command::eReqMove);
        if(m_game->PlayersTank(bean->PlayerId())->CurrentAction() == Tank::eMoving)
            m_game->PlayersTank(bean->PlayerId())->SetAction(Tank::eStopping);
        break;
    }

    case Command::eReqFire:
    {
        PlayerIDBean* bean = static_cast<PlayerIDBean*>(request.data()->Params().data());
        m_game->PlayersTank(bean->PlayerId())->SetFiring(true);
        break;
    }

    case Command::eReqStopFire:
    {
        PlayerIDBean* bean = static_cast<PlayerIDBean*>(request.data()->Params().data());
        m_game->PlayersTank(bean->PlayerId())->SetFiring(false);
        break;
    }

    case Command::eReqTurnLeft:
    {
        PlayerIDBean* bean = static_cast<PlayerIDBean*>(request.data()->Params().data());
        AddCommand(m_game->PlayersTank(bean->PlayerId())->Id(), Command::eReqTurnLeft);
        break;
    }

    case Command::eReqTurnRight:
    {
        PlayerIDBean* bean = static_cast<PlayerIDBean*>(request.data()->Params().data());
        AddCommand(m_game->PlayersTank(bean->PlayerId())->Id(), Command::eReqTurnRight);
        break;
    }

    }
}
